﻿using System;
using System.Data;

namespace BizElements.Core
{
    /// <summary>Extends <see cref="IEntity"/> objects.</summary>
    public static class EntityExtensions
    {
        #region Conversion - IRecordTransformer.

        /// <summary>Initializes entity members with data stored in the given DataRow.</summary>
        /// <param name="entity">Entity.</param>
        /// <param name="row">DataRow with the required columns.</param>
        public static void FromDataRow(this IEntity entity, DataRow row)
        {
            RecordTransformerFactory.GetTransformer(entity).FromDataRow(row);
        }

        /// <summary>Initializes entity members with data stored in the given DataRow.</summary>
        /// <param name="entity">Entity.</param>
        /// <param name="row">DataRow with all or some of the columns defined in meta data.</param>
        /// <param name="fieldMetadata"><see cref="IDbTable"/> meta data object which links ADO.NET row columns to entity properties.</param>
        /// <remarks>This method, when used in combination with <see cref="IDbTable"/> objects that contain columns with
        /// prefixed aliases, enables you to initialize multiple entities that belong to different tables with data from a single DataRow.</remarks>
        public static void FromDataRow(this IEntity entity, DataRow row, IDbTable fieldMetadata)
        {
            RecordTransformerFactory.GetTransformer(entity).FromDataRow(row, fieldMetadata);
        }

        /// <summary>Creates an array of objects containing entity data suitable for copying and importing into <see cref="DataTable"/> </summary>	
        /// <param name="entity">Entity.</param>
        /// <returns>Entity values.</returns>	
        public static object[] ToObjectArray(this IEntity entity)
        {
            return RecordTransformerFactory.GetTransformer(entity).ToObjectArray();
        }

        /// <summary>Initializes entity members with the given values.</summary>
        /// <param name="entity">Entity.</param>
        /// <param name="entityValues">Array with the required values.</param>	
        public static void FromObjectArray(this IEntity entity, object[] entityValues)
        {
            RecordTransformerFactory.GetTransformer(entity).FromObjectArray(entityValues);
        }

        #endregion

        #region Validation - IEntityValidator.

        /// <summary>
        /// Validates all fields of an entity. Returns all encountered errors.
        /// </summary>
        /// <param name="entity">Entity.</param>
        /// <returns>Array of FieldError objects that describe all encountered errors. Empty array if there are no errors.</returns>
        public static FieldError[] ValidateAllFields(this IEntity entity)
        {
            return EntityValidatorFactory.GetValidator(entity).ValidateAllFields();
        }

        /// <summary>
        /// Returns a value which indicates whether all fields of an entity contain valid values. 
        /// </summary>
        /// <param name="entity">Entity.</param>
        /// <returns>True if all fields are valid; false otherwise.</returns>
        public static bool IsValidEntity(this IEntity entity)
        {
            return EntityValidatorFactory.GetValidator(entity).IsValidEntity();
        }

        /// <summary>
        /// Validates specified field.
        /// </summary>
        /// <param name="entity">Entity.</param>
        /// <param name="field">Field mapped in this entity.</param>
        /// <remarks>Checks whether the value contained in the given field obeys basic constraints 
        /// (nullability, min/max value etc). Returns <see cref="FieldErrorCode"/> that indicates 
        /// the type of error encountered.</remarks>
        /// <returns>Error code that indicates the type of encountered error.</returns>
        public static FieldErrorCode ValidateField(this IEntity entity, IDbColumn field)
        {
            return EntityValidatorFactory.GetValidator(entity).ValidateField(field);
        }

        /// <summary>
        /// Returns a value which indicates whether the specifified is valid.
        /// Checks whether the value contained in the given field obeys basic constraints (nullability, min/max value etc).
        /// </summary>
        /// <param name="entity">Entity.</param>
        /// <param name="field">Field mapped in this entity.</param>
        /// <returns>True if the field is valid; false otherwise.</returns>
        public static bool IsValidField(this IEntity entity, IDbColumn field)
        {
            return EntityValidatorFactory.GetValidator(entity).IsValidField(field);
        }

        #endregion

        #region Clone - ICloneable.

        /// <summary>Copies entity.</summary>
        /// <param name="entity">Entity.</param>
        /// <returns>Clone.</returns>
        public static object Clone(this IEntity entity)
        {
            ICloneable cloneableEntity = entity as ICloneable;
            if (cloneableEntity != null)
                return cloneableEntity.Clone();

            IDbTable tbl = entity.Table;
            IEntity clone = tbl.NewEntity();
            clone.FromObjectArray(entity.ToObjectArray());
            foreach (DbRelation fk in tbl.ForeignKeys)
            {
                IEntity parent = entity.GetParent(fk);
                if (parent != null)
                    clone.SetParent(fk, parent);
            }

            return clone;
        }

        #endregion

        #region PK - IEntityKeybinder.

        /// <summary>
        /// Gets the value(s) that uniquely identify an entity.
        /// In the order as specified in the accompanying IDbTable class.
        /// <b>Null</b> if the parent table/view doesn't have a primary key constraint or the required fields are not set.
        /// </summary>
        /// <param name="entity">Entity.</param>
        /// <returns>An object array that holds Primary Key values.</returns>
        public static object[] GetPrimaryKeyValue(this IEntity entity)
        {
            return EntityKeyBinderFactory.GetKeyBinder(entity).GetPrimaryKeyValue();
        }

        /// <summary>
        /// Checks if the given values are equal to the primary key value of this entity.
        /// </summary>
        /// <param name="entity">Entity.</param>
        /// <param name="values">Values.</param>
        /// <returns>True if primary key values match the given values; false otherwise or if the PK fields 
        /// of this entity aren't set.</returns>
        public static bool PrimaryKeyEquals(this IEntity entity, object[] values)
        {
            return EntityKeyBinderFactory.GetKeyBinder(entity).PrimaryKeyEquals(values);
        }

        #endregion

        #region Properties mapped to columns and relations - getters and setters. Null-check behavior.

        /// <summary>
        /// Resets the specified field to default value.
        /// Behaviour should be the same as using the SetField method with a null argument.
        /// </summary>		
        /// <param name="entity">Entity.</param>
        /// <param name="field">Field that is to be reset.</param>
        public static void ResetField(this IEntity entity, IDbColumn field)
        {
            EntityPropertyBinderFactory.GetBinder(entity).ResetField(field);
        }

        /// <summary>
        /// Sets the given value into specified field.
        /// </summary>
        /// <param name="entity">Entity.</param>
        /// <param name="field">Field.</param>
        /// <param name="value">Value. If null then the field is reset to default value.</param>
        public static void SetField(this IEntity entity, IDbColumn field, object value)
        {
            EntityPropertyBinderFactory.GetBinder(entity).SetField(field, value);
        }

        /// <summary>
        /// Gets the value contained in the specified field.
        /// </summary>
        /// <param name="entity">Entity.</param>
        /// <param name="field">Field.</param>
        /// <returns>Value.</returns>
        public static object GetField(this IEntity entity, IDbColumn field)
        {
            return EntityPropertyBinderFactory.GetBinder(entity).GetField(field);
        }

        /// <summary>
        /// Checks whether the specified field contains a <b>null</b> reference or it hasn't been initialized.
        /// </summary>
        /// <param name="entity">Entity.</param>
        /// <param name="field">Field. Throws exception if an invalid field is given.</param>
        /// <returns><b>true</b> if the field contains <b>null</b> not initialized; <b>false</b> otherwise.</returns>
        public static bool IsNull(this IEntity entity, IDbColumn field)
        {
            return EntityPropertyBinderFactory.GetBinder(entity).IsNull(field);
        }

        /// <summary>
        /// Sets the given value into the member that represents the parent entity defined by the foreign key.
        /// </summary>
        /// <param name="entity">Entity.</param>
        /// <param name="foreignKey">FK.</param>
        /// <param name="parentEntity">Parent entity.</param>
        public static void SetParent(this IEntity entity, DbRelation foreignKey, IEntity parentEntity)
        {
            EntityPropertyBinderFactory.GetBinder(entity).SetParent(foreignKey, parentEntity);
        }

        /// <summary>
        /// Gets the parent entity defined by the given foreign key.
        /// </summary>
        /// <param name="entity">Entity.</param>
        /// <param name="foreignKey">FK.</param>
        /// <returns>Parent entity. <b>Null</b> if the FK fields haven't been set or if the entity with the given key values doesn't exist.</returns>
        /// <remarks>If a class implements data-access logic, and if the entity hasn't yet been initialized but the 
        /// FK values are set then the method will automatically fetch it from the data source.</remarks>
        public static IEntity GetParent(this IEntity entity, DbRelation foreignKey)
        {
            return EntityPropertyBinderFactory.GetBinder(entity).GetParent(foreignKey);
        }

        #endregion

        /// <summary>Checks if all foreign key fields are set to a non-null value.</summary>
        /// <param name="entity">Child entity.</param>
        /// <param name="fk">Foreign key.</param>
        /// <returns><b>true</b> if all FK parts are non-null; otherwise <b>false</b></returns>
        public static bool IsForeignKeySet(this IEntity entity, DbRelation fk)
        {
            foreach (IDbColumn fkPart in fk.ChildForeignKey)
            {
                if (entity.IsNull(fkPart))
                    return false;
            }

            return true;
        }
    }
}
